﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Collections.Specialized;
using System.IO;
using System.Globalization;
using System.Xml.Serialization;

namespace blackhouse.WebNemo.Business.Groups {

    [DebuggerDisplay("{RecordId} - {Identyficator} (w grupie {group.Name})")]
    public class GroupRecord : IGroupRecord {

        #region Fields

        private IGroupRecord record;
        private IGroup group;
        private Factory factory = Factory.Instance();
        private RecordExtension extension;

        #endregion

        #region Properties

        [XmlIgnore]
        public RecordExtension Extension {
            get {
                if (extension == null)
                    extension = RecordExtension.GetRecordExtension(this.GetGroup(), this);
                return extension;
            }
        }

        public string Identyficator {
            get { return this.record.Identyficator; }
        }

        public int RecordId {
            get { return this.record.RecordId; }
        }

        public int BelongsToGroupId {
            get { return this.record.BelongsToGroupId; }
        }

        public DateTime CreatedTime {
            get { return this.record.CreatedTime; }
        }

        public int CreatedBy {
            get { return this.record.CreatedBy; }
        }

        public DateTime LastModifiedTime {
            get {
                return
                    (this.record.LastModifiedTime == DateTime.MinValue) ?
                    this.record.CreatedTime :
                    this.record.LastModifiedTime;
            }
        }

        public int LastModifiedBy {
            get { return this.record.LastModifiedBy; }
        }

        public object this[string column] {
            get { return this.record[column]; }
        }

        public bool Exists { get; private set; }

        [XmlIgnore]
        public CultureInfo RecordCulture { get; private set; }

        #endregion

        #region Constructors

        public GroupRecord(int groupId, int recordId)
            : this(groupId, recordId, CultureInfo.CurrentCulture) { }

        public GroupRecord(int groupId, int recordId, CultureInfo operatedLanguage) {
            this.RecordCulture = operatedLanguage;
            this.group = this.factory.GetGroup(groupId);
            if (recordId > 0)
                this.record = this.group.GetGroupRecord(recordId, operatedLanguage);
            this.Exists = this.record != null;
            if (!this.Exists) this.record = this.group.GetNewGroupRecord();
        }

        public GroupRecord(GroupRecordConstructor constructor)
            : this(constructor, CultureInfo.CurrentCulture) { }

        public GroupRecord(GroupRecordConstructor constructor, CultureInfo operatedLanguage) {
            this.RecordCulture = operatedLanguage;
            this.group = constructor.Group;
            this.record = constructor.Record;
            this.Exists = this.record != null;
            if (!this.Exists) this.record = this.group.GetNewGroupRecord();
        }

        public GroupRecord(string groupName, int recordId)
            : this(groupName, recordId, CultureInfo.CurrentCulture) { }

        public GroupRecord(string groupName, int recordId, CultureInfo operatedLanguage)
            : this(GetGroupId(groupName), recordId, operatedLanguage) { }

        public GroupRecord(IGroup group, IGroupRecord record)
            : this(group, record, CultureInfo.CurrentCulture) { }

        public GroupRecord(IGroup group, IGroupRecord record, CultureInfo operatedLanguage) {
            this.SetObject(group, record, operatedLanguage);
        }

        protected GroupRecord() { }

        #endregion

        #region Methods

        internal void SetObject(IGroup group, IGroupRecord record) {
            this.SetObject(group, record, CultureInfo.CurrentCulture);
        }

        internal void SetObject(IGroup group, IGroupRecord record, CultureInfo operatedLanguage) {
            this.RecordCulture = operatedLanguage;
            this.group = group;
            this.record = record;
            this.Exists = this.record != null && this.record.RecordId > 0;
            if (!this.Exists) this.record = this.group.GetNewGroupRecord();
        }

        private static int GetGroupId(string groupName) {
            IGroupOperations opera = Factory.Instance().GetGroupOperators();
            int groupId = opera.GetGroupId(groupName);
            if (groupId == 0)
                throw new Exception("Grupa " + groupName + " nie została odnaleziona");
            return groupId;
        }

        public object GetValue(string column) {
            return this.record.GetValue(column);
        }

        public object SetNullValue(string column) {
            return this.SetValue(column, System.DBNull.Value);
        }

        public object SetValue(string column, object value) {
            if (record != null)
                return this.record.SetValue(column, value);
            else
                return null;
        }

        public void SetFileValue(string column, string fileName, Stream fileStream) {
            this.SetValue(column, new SaveFile(fileName, fileStream));
        }

        public DateTime SetValue(string column, DateTime value) {
            switch (this.GetGroup().Columns[column].ColumnType) {
                case FieldType.DateField:
                    return this.SetValueD(column, value);
                case FieldType.DateTimeField:
                    return this.SetValueDT(column, value);
                default:
                    return this.GetDateTime(column);
            }
        }

        private DateTime SetValueD(string column, DateTime value) {
            object prevValue;
            if (value == DateTime.MinValue)
                prevValue = this.record.SetValue(column, DBNull.Value);
            else {
                var val = Convert.ToInt32((value.Date - new DateTime(2001, 01, 01)).TotalDays + 73053);
                prevValue = this.record.SetValue(column, val);
            }
            if (prevValue == null || prevValue is DBNull) return DateTime.MinValue;
            var pval = (int)prevValue;
            return this.ConvertToDateTimeFromD(pval);
        }

        private DateTime SetValueDT(string column, DateTime value) {
            object prevValue;
            if (value == DateTime.MinValue)
                prevValue = this.record.SetValue(column, DBNull.Value);
            else {
                var val = Convert.ToInt32((value - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds);
                prevValue = this.record.SetValue(column, val);
            }
            if (prevValue == null || prevValue is DBNull) return DateTime.MinValue;
            var pval = (int)prevValue;
            return this.ConvertToDateTimeFromDT(pval);
        }

        public bool IsUpdated(string column) {
            return this.record.IsUpdated(column);
        }

        public void ChangeIdentyficator(string ident) {
            this.record.ChangeIdentyficator(ident);
        }

        public bool IsNull(string column) {
            if (column.EndsWith("_name") && !this.group.Columns.Any(gc => gc.ColumnName == column))
                column = column.Substring(0, column.Length - 5);

            switch (this.group.Columns[column].ColumnType) {
                case FieldType.ValueField:
                case FieldType.DateField:
                case FieldType.DateTimeField:
                    object obj = this[column];
                    return this.TestNull(obj);
                case FieldType.OneReference:
                    object objo = this[column];
                    return TestNull(objo) || Convert.ToInt32(objo) == 0;
                case FieldType.ManyReference:
                    return false;
                case FieldType.FileField:
                    object objf = this[column + "_name"];
                    return this.TestNull(objf) || string.IsNullOrEmpty(objf.ToString());
                default:
                    return false;
            }

        }

        private bool TestNull(object obj) {
            return obj == null || obj is DBNull;
        }

        public bool IsNotNull(string column) {
            return !this.IsNull(column);
        }

        public string GetString(string column) {
            return Convert.ToString(this[column]);
        }

        public string GetStringOrDefault(string column) {
            return this.GetStringOrDefault(column, String.Empty);
        }

        public string GetStringOrDefault(string column, string defaultValue) {
            return this.IsNotNull(column) ? this.GetString(column) : defaultValue;
        }

        public string GetFileName(string column) {
            if (!column.EndsWith("_name"))
                column = column + "_name";
            return Convert.ToString(this[column]);
        }

        public int GetInt32(string column) {
            return Convert.ToInt32(this[column]);
        }

        public int GetInt32OrDefault(string column) {
            return this.GetInt32OrDefault(column, 0);
        }

        public int GetInt32OrDefault(string column, int defaultValue) {
            return this.IsNotNull(column) ? this.GetInt32(column) : defaultValue;
        }

        public float GetSingle(string column) {
            return Convert.ToSingle(this[column]);
        }

        public float GetSingleOrDefault(string column) {
            return this.GetSingleOrDefault(column, 0);
        }

        public float GetSingleOrDefault(string column, float defaultValue) {
            return this.IsNotNull(column) ? this.GetSingle(column) : defaultValue;
        }

        public double GetDouble(string column) {
            return Convert.ToDouble(this[column]);
        }

        public double GetDoubleOrDefault(string column) {
            return this.GetDoubleOrDefault(column, 0);
        }

        public double GetDoubleOrDefault(string column, double defaultValue) {
            return this.IsNotNull(column) ? this.GetDouble(column) : defaultValue;
        }

        public decimal GetDecimal(string column) {
            return Convert.ToDecimal(this[column]);
        }

        public decimal GetDecimalOrDefault(string column, decimal defaultValue = 0) {
            return this.IsNotNull(column) ? this.GetDecimal(column) : defaultValue;
        }

        public bool GetBoolean(string column) {
            return this.GetBooleanOrDefault(column);
        }

        public bool GetBooleanOrDefault(string column, bool defaultValue = false) {
            if (this.IsNotNull(column))
                return Convert.ToBoolean(this[column]);
            var gc = this.GetGroup().Columns[column];
            return
                this.TestNull(gc.DefaultValue)
                    ? defaultValue
                    : Convert.ToBoolean(gc.DefaultValue);
        }

        public DateTime GetDateTime(string column) {
            switch (this.group.Columns[column].ColumnType) {
                case FieldType.DateField:
                    return GetDateTimeFromD(column);
                case FieldType.DateTimeField:
                    return GetDateTimeFromDT(column);
                default:
                    return DateTime.MinValue;
            }
        }

        public DateTime GetDateTimeOrDefault(string column) {
            return this.GetDateTimeOrDefault(column, DateTime.MinValue);
        }

        public DateTime GetDateTimeOrDefault(string column, DateTime defaultValue) {
            return this.IsNotNull(column) ? this.GetDateTime(column) : defaultValue;
        }

        public T GetCustomValue<T>(string columnName)
        where T : class {
            return this.GetValue(columnName) as T;
        }

        private DateTime GetDateTimeFromD(string column) {
            return this.ConvertToDateTimeFromD(GetInt32(column));
        }

        private DateTime ConvertToDateTimeFromD(int dbValue) {
            return new DateTime(2001, 1, 1) + TimeSpan.FromDays(dbValue - 73053);
        }

        private DateTime GetDateTimeFromDT(string column) {
            return this.ConvertToDateTimeFromDT(GetInt32(column));
        }

        private DateTime ConvertToDateTimeFromDT(int dbValue) {
            return new DateTime(1970, 1, 1) + TimeSpan.FromSeconds(dbValue);
        }

        private Dictionary<string, GroupRecord> storedRecord = new Dictionary<string, GroupRecord>();

        //public GroupRecord GetRecord(string column) {
        //    return this.GetRecord<GroupRecord>(column);
        //}

        public T GetRecord<T>(string column) where T : GroupRecord, new() {
            RecordConverter<T> converter = new RecordConverter<T>();
            return converter.Convert(this.GetRecord(column));
        }

        public GroupRecord GetRecord(string column) {
            if (storedRecord.ContainsKey(column))
                return storedRecord[column];
            GroupRecord record = null;
            if (this.IsNotNull(column)) {
                int recordId = 0;
                recordId = this.GetInt32(column);
                record = new GroupRecord(this.GetGroup().Columns[column].RelatedGroupId, recordId, this.RecordCulture);
            }
            if (record != null && record.Exists)
                storedRecord.Add(column, record);
            return record;
        }

        //public T GetRecord<T>(string column) where T : GroupRecord {
        //    if (storedRecord.ContainsKey(column))
        //        return storedRecord[column] as T;
        //    try {
        //        int recordId = this.GetInt32(column);
        //        Type t = typeof(T);

        //        if (t.GetConstructor(new Type[] { typeof(Int32), typeof(Int32) }) != null) {
        //            T obj = Activator.CreateInstance(t, this.GetGroup().Columns[column].RelatedGroupId, recordId) as T;
        //            if (obj != null)
        //                storedRecord.Add(column, obj);
        //            return obj;
        //        }
        //        else if (t.GetConstructor(new Type[] { typeof(Int32) }) != null) {
        //            T obj = Activator.CreateInstance(t, recordId) as T;
        //            if (obj != null)
        //                storedRecord.Add(column, obj);
        //            return obj;
        //        }
        //        else
        //            return null;
        //    }
        //    catch {
        //        return null;
        //    }
        //}

        private Dictionary<string, GroupRecord[]> storedRecords = new Dictionary<string, GroupRecord[]>();
        private Dictionary<string, GroupRecord[]> storedDbRecords = new Dictionary<string, GroupRecord[]>();

        public GroupRecord[] GetRecords(string column) {
            if (!storedRecords.ContainsKey(column))
                storedRecords.Add(column, this.GetDbRecords(column));
            return storedRecords[column];
        }

        public T[] GetRecords<T>(string column) where T : GroupRecord, new() {
            RecordConverter<T> converter = new RecordConverter<T>();
            return converter.ConvertAll(this.GetRecords(column));
        }

        //private GroupRecord[] GetDbRecords(string column) {
        //    return this.GetDbRecords<GroupRecord>(column);
        //}

        private GroupRecord[] GetDbRecords(string column) {
            if (!storedDbRecords.ContainsKey(column)) {
                GroupColumn gc = this.group.Columns[column];
                if (gc != null) {
                    IGroup relGroup = this.factory.GetGroup(gc.RelatedGroupId);
                    IGroupRecord[] records = relGroup.GetRelatedGroupRecords(this.group.Id, column, this.RecordId, this.RecordCulture);
                    return records.Select(r => new GroupRecord(relGroup, r)).ToArray();
                } else
                    storedDbRecords.Add(column, new GroupRecord[0]);
            }
            return storedDbRecords[column];

        }

        //private T[] GetDbRecords<T>(string column) where T : GroupRecord {
        //    if (!storedDbRecords.ContainsKey(column)) {
        //        GroupColumn gc = this.group.Columns[column];
        //        if (gc != null) {
        //            IGroup relGroup = this.factory.GetGroup(gc.RelatedGroupId);
        //            IGroupRecord[] records = relGroup.GetRelatedGroupRecords(this.group.Id, column, this.RecordId, this.RecordCulture);
        //            Type t = typeof(T);
        //            if (t.GetConstructor(new Type[] { typeof(IGroup), typeof(IGroupRecord) }) != null) {
        //                List<T> tRecords = new List<T>();
        //                foreach (IGroupRecord record in records) {
        //                    T tRecord = Activator.CreateInstance(t, relGroup, record) as T;
        //                    tRecords.Add(tRecord);
        //                }
        //                return tRecords.ToArray();
        //            }
        //            else
        //                throw new InvalidCastException("Brak konstruktora ctor(IGroup, IGroupRecord)");
        //        }
        //        else
        //            storedDbRecords.Add(column, new T[0]);
        //    }
        //    return storedDbRecords[column].Cast<T>().ToArray();

        //}

        public void SetRecords(string column, int[] records) {
            GroupColumn gc = this.group.Columns[column];
            if (gc != null)
                this.SetRecords(column, records.Select(i => new GroupRecord(gc.RelatedGroupId, i)).ToArray());
        }

        public void SetRecord(string column, int recordId) {
            GroupColumn gc = this.group.Columns[column];
            if (gc != null)
                this.storedRecord[column] = new GroupRecord(gc.RelatedGroupId, recordId);
        }

        public void SetRecord(string column, GroupRecord record) {
            this.storedRecord[column] = record;
        }

        private StringCollection referencesColumnModified = new StringCollection();

        public void SetRecords(string column, GroupRecord[] records) {
            this.storedRecords[column] = records.Where(r => r.Exists).ToArray();
            if (!this.referencesColumnModified.Contains(column))
                this.referencesColumnModified.Add(column);
        }

        public IGroup GetGroup() {
            return this.group;
        }

        public void Remove() {
            this.record.Remove();
            this.Extension.RecordChanged(RecordChangeType.Deleted);
        }

        public void Save() {
            this.Save(true, 0);
        }

        public void Save(int userId) {
        }

        public void Save(bool raiseExtensions) {
            this.Save(raiseExtensions, 0);
        }

        public void Save(bool raiseExtensions, int userId) {
            bool prevExist = this.Exists;

            if (prevExist)
                this.SaveOneReferences(userId);

            if (userId > 0)
                this.record.Save(userId);
            else
                this.record.Save();

            if (!prevExist) {
                this.SaveOneReferences(userId);
                if (userId > 0)
                    this.record.Save(userId);
                else
                    this.record.Save();
            }
            var groupType = this.GetGroup().Type;
            if (groupType == GroupType.Table && string.IsNullOrEmpty(this.Identyficator))
                this.ChangeIdentyficator(this.GetDefaultIdentyficator());
            this.SaveReferences();

            if (raiseExtensions && this.Extension != RecordExtension.NULL) {
                RecordChangeType type = (prevExist) ? RecordChangeType.Updated : RecordChangeType.Added;
                this.Extension.RecordChanged(type);
            }

            this.Exists = true;

        }

        private string GetDefaultIdentyficator() {
            if (this.Extension != RecordExtension.NULL) {
                string rei = this.Extension.GetIdentyficator();
                if (!string.IsNullOrEmpty(rei))
                    return rei;
            }

            IGroup g = this.GetGroup();
            var cs = g.Columns.Where(c => c.ColumnType != FieldType.FileField || c.ColumnType != FieldType.DateField || c.ColumnType != FieldType.DateTimeField);
            cs = cs.OrderBy(c => c.ColumnSort).ThenBy(c => c.ColumnName);
            List<string> vals = new List<string>();
            foreach (GroupColumn c in cs) {
                string val = "";

                switch (c.ColumnType) {
                    case FieldType.ValueField:
                        if (this.IsNotNull(c.ColumnName) && c.DataType == typeof(string))
                            val = this[c.ColumnName].ToString();
                        break;
                    case FieldType.OneReference:
                        if (this.IsNotNull(c.ColumnName)) {
                            GroupRecord gr = this.GetRecord(c.ColumnName);
                            if (gr != null && gr.Exists)
                                val = gr.Identyficator;
                        }
                        break;
                }

                if (!string.IsNullOrEmpty(val)) {
                    vals.Add(val);
                    if (vals.Count == 3) break;
                }

            }
            string i = string.Join(" - ", vals.ToArray()).RemoveHtml().RemovePunctuation();
            if (i.Length > 200)
                i = i.Substring(0, 200);

            return i;
        }

        public void UpdateIdentyficator() {
            this.ChangeIdentyficator(this.GetDefaultIdentyficator());
        }

        private void SaveOneReferences(int userId) {
            foreach (KeyValuePair<string, GroupRecord> record in this.storedRecord) {
                if (record.Value == null || record.Value.RecordId == 0) {
                    // Przy usuwaniu przypisania mamy tutaj 0, przez co aplikacja próbuje zapisać ten rekord. Ten warunek wyklucza ten błąd
                    this.SetValue(record.Key, DBNull.Value);
                    continue;
                }
                if (userId == 0)
                    record.Value.Save();
                else
                    record.Value.Save(userId);
                this.SetValue(record.Key, record.Value.RecordId);
            }
            this.storedRecord.Clear();
        }

        private void SaveReferences() {
            foreach (string column in this.referencesColumnModified) {

                GroupRecord[] dbRecords = this.GetDbRecords(column);
                GroupRecord[] storedRecords = this.GetRecords(column);

                foreach (GroupRecord recordToDel in dbRecords.Except(storedRecords)) // rekordy które są w bazie, ale nie ma już ich w obiekcie
                    recordToDel.GetGroup().RemoveRelationToGroupRecord(recordToDel.RecordId, this.BelongsToGroupId, column, this.RecordId);

                foreach (GroupRecord recordToDel in storedRecords.Except(dbRecords)) // rekordy które są w obiekcie, ale nie ma ich w bazie i trzeba dodać
                    recordToDel.GetGroup().AddRelationToGroupRecord(recordToDel.RecordId, this.BelongsToGroupId, column, this.RecordId);

            }

            this.storedDbRecords.Clear();
            this.storedRecords.Clear();

        }
        public override bool Equals(object obj) {
            if (obj == null)
                return false;

            GroupRecord p = obj as GroupRecord;
            if ((object)p == null)
                return false;

            return (BelongsToGroupId == p.BelongsToGroupId) && (RecordId == p.RecordId);
        }

        public bool Equals(GroupRecord p) {
            if ((object)p == null)
                return false;

            return (BelongsToGroupId == p.BelongsToGroupId) && (RecordId == p.RecordId);
        }

        public override int GetHashCode() {
            return BelongsToGroupId * 100000 + RecordId;
        }

        #endregion

    }
}
